// 版权归GoFrame作者(https://goframe.org)所有。保留所有权利。
//
// 本源代码形式受MIT许可证条款约束。
// 如果未随本文件一同分发MIT许可证副本，
// 您可以在https://github.com/gogf/gf处获取。
// md5:a9832f33b234e3f3

package 进程类

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strings"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/trace"

	"gitee.com/go_888/goframe"
	gcode "gitee.com/go_888/goframe/errors/gcode"
	gerror "gitee.com/go_888/goframe/errors/gerror"
	"gitee.com/go_888/goframe/internal/intlog"
	"gitee.com/go_888/goframe/net/gtrace"
	genv "gitee.com/go_888/goframe/os/genv"
	gstr "gitee.com/go_888/goframe/text/gstr"
)

// X结构_进程 是表示单个进程的结构体。 md5:f6524ce6eee4a18b
type X结构_进程 struct {
	exec.Cmd
	Manager *X结构_Manager
	PPid    int
}

// X创建进程 创建并返回一个新的 Process。 md5:dbd46312fa39f087
func X创建进程(进程路径 string, 参数数组 []string, 环境变量 ...[]string) *X结构_进程 {
	env := os.Environ()
	if len(环境变量) > 0 {
		env = append(env, 环境变量[0]...)
	}
	process := &X结构_进程{
		Manager: nil,
		PPid:    os.Getpid(),
		Cmd: exec.Cmd{
			Args:       []string{进程路径},
			Path:       进程路径,
			Stdin:      os.Stdin,
			Stdout:     os.Stdout,
			Stderr:     os.Stderr,
			Env:        env,
			ExtraFiles: make([]*os.File, 0),
		},
	}
	process.Dir, _ = os.Getwd()
	if len(参数数组) > 0 {
		// 排除当前二进制文件的路径。 md5:a174ba38ac49d432
		start := 0
		if strings.EqualFold(进程路径, 参数数组[0]) {
			start = 1
		}
		process.Args = append(process.Args, 参数数组[start:]...)
	}
	return process
}

// X创建cmd进程 创建并返回一个具有给定命令和可选环境变量数组的进程。 md5:01376a1e29c9935e
func X创建cmd进程(命令 string, 环境变量 ...[]string) *X结构_进程 {
	return X创建进程(getShell(), append([]string{getShellOption()}, parseCommand(命令)...), 环境变量...)
}

// X异步运行 以非阻塞方式开始执行进程。
// 如果成功，它将返回进程ID（pid），否则返回一个错误。
// md5:4607fc00f35e6338
func (p *X结构_进程) X异步运行(上下文 context.Context) (int, error) {
	if p.Process != nil {
		return p.X取进程ID(), nil
	}
	// 为命令提供OpenTelemetry。 md5:46407dd5b38f692f
	var (
		span trace.Span
		tr   = otel.GetTracerProvider().Tracer(
			tracingInstrumentName,
			trace.WithInstrumentationVersion(gf.VERSION),
		)
	)
	上下文, span = tr.Start(
		otel.GetTextMapPropagator().Extract(
			上下文,
			propagation.MapCarrier(genv.X取Map()),
		),
		gstr.X连接(os.Args, " "),
		trace.WithSpanKind(trace.SpanKindInternal),
	)
	defer span.End()
	span.SetAttributes(gtrace.CommonLabels()...)

	// OpenTelemetry 传播。 md5:aecf3a0cccd13f96
	tracingEnv := tracingEnvFromCtx(上下文)
	if len(tracingEnv) > 0 {
		p.Env = append(p.Env, tracingEnv...)
	}
	p.Env = append(p.Env, fmt.Sprintf("%s=%d", envKeyPPid, p.PPid))
	p.Env = genv.X数组去重(p.Env)

	// 在 Windows 系统中，这可以工作，但在其他平台则无法工作. md5:9aac240ca7d717fe
	if runtime.GOOS == "windows" {
		joinProcessArgs(p)
	}

	if err := p.Cmd.Start(); err == nil {
		if p.Manager != nil {
			p.Manager.processes.X设置值(p.Process.Pid, p)
		}
		return p.Process.Pid, nil
	} else {
		return 0, err
	}
}

// X同步运行以阻塞方式执行进程。 md5:aeab1ddf5fca3d31
func (p *X结构_进程) X同步运行(上下文 context.Context) error {
	if _, err := p.X异步运行(上下文); err == nil {
		return p.Wait()
	} else {
		return err
	}
}

// X取进程ID 获取并返回进程的PID。 md5:7f6e89391a9d1aac
func (p *X结构_进程) X取进程ID() int {
	if p.Process != nil {
		return p.Process.Pid
	}
	return 0
}

// X发送数据 向进程发送自定义数据。 md5:cb2381344fb13fd4
func (p *X结构_进程) X发送数据(数据 []byte) error {
	if p.Process != nil {
		return X发送数据(p.Process.Pid, 数据)
	}
	return gerror.X创建错误码(gcode.X变量_CodeInvalidParameter, "无效的进程")
}

// X释放资源 释放与进程 p 关联的任何资源，使其将来无法使用。
// 只有在不调用 Wait 的情况下才需要调用 X释放资源。
// md5:f3540c25ba14f0ee
func (p *X结构_进程) X释放资源() error {
	return p.Process.Release()
}

// X结束进程 立即导致 Process 终止。 md5:4bacb16ab3b9aebe
func (p *X结构_进程) X结束进程() (错误 error) {
	错误 = p.Process.Kill()
	if 错误 != nil {
		错误 = gerror.X多层错误并格式化(错误, `终止 PID "%d" 的进程失败`, p.Process.Pid)
		return 错误
	}
	if p.Manager != nil {
		p.Manager.processes.X删除(p.X取进程ID())
	}
	if runtime.GOOS != "windows" {
		if 错误 = p.Process.Release(); 错误 != nil {
			intlog.Errorf(context.TODO(), `%+v`, 错误)
		}
	}
	// 它忽略这个错误，仅记录日志。 md5:578bff85a58d16e8
	_, 错误 = p.Process.Wait()
	intlog.Errorf(context.TODO(), `%+v`, 错误)
	return nil
}

// X发送信号 向进程发送一个信号。
// 在Windows上发送Interrupt信号未实现。
// md5:c1afe56a9d236095
func (p *X结构_进程) X发送信号(信号 os.Signal) error {
	return p.Process.Signal(信号)
}
